package com.riven.redisson.config;

import com.riven.redisson.consts.ServerType;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.*;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Role;
import org.springframework.context.annotation.Scope;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @author riven
 */
@AutoConfiguration
@ConditionalOnClass({RedissonClient.class, Redisson.class})
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {

    @Scope(BeanDefinition.SCOPE_SINGLETON)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @Bean(name = RedissonConfigUtils.REDISSON_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
    public RedissonAnnotationBeanPostProcessor redissonAnnotationBeanPostProcessor() {
        return new RedissonAnnotationBeanPostProcessor();
    }

    @Scope(BeanDefinition.SCOPE_SINGLETON)
    @Bean(name = RedissonConfigUtils.REDISSON_LISTENER_REGISTRY_BEAN_NAME)
    public RedissonListenerRegistry redissonListenerRegistry() {
        return new RedissonListenerRegistry();
    }

    @Scope(BeanDefinition.SCOPE_SINGLETON)
    @Bean(name = RedissonConfigUtils.REDISSON_QUEUE_BEAN_PROCESSOR_BEAN_NAME)
    public RedissonQueueBeanPostProcessor redissonQueueBeanPostProcessor() {
        return new RedissonQueueBeanPostProcessor();
    }

    @Scope(BeanDefinition.SCOPE_SINGLETON)
    @Bean(name = RedissonConfigUtils.REDISSON_QUEUE_REGISTRY_BEAN_NAME)
    public RedissonQueueRegistry redissonQueueRegistry() {
        return new RedissonQueueRegistry();
    }

    @Scope(BeanDefinition.SCOPE_SINGLETON)
    @Bean
    @ConditionalOnMissingBean
    public RedissonTemplate redissonTemplate() {
        return new RedissonTemplate();
    }

    @Scope(BeanDefinition.SCOPE_SINGLETON)
    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean
    public RedissonClient redissonClient(RedissonProperties properties,
                                         ObjectProvider<RedissonConfigCustomizer[]> objectProvider) {
        final ServerType serverType = properties.getServerType();
        final String serverAddress = properties.getServerAddress();
        final String username = properties.getUsername();
        final String password = properties.getPassword();
        final int database = properties.getDatabase();
        Config config = new Config();
        config.setThreads(properties.getThreads());
        config.setNettyThreads(properties.getNettyThreads());
        config.setLockWatchdogTimeout(properties.getLockWatchdogTimeoutMillis());
        BaseConfig<?> baseConfig;
        switch (serverType) {
            case SINGLE, STANDALONE -> {
                SingleServerConfig singleServerConfig = config.useSingleServer();
                singleServerConfig.setAddress(checkAndFixAddress(serverAddress));
                singleServerConfig.setDatabase(database);
                singleServerConfig.setConnectionPoolSize(properties.getMaxPoolSize());
                singleServerConfig.setConnectionMinimumIdleSize(properties.getMinIdleSize());
                baseConfig = singleServerConfig;
            }
            case MASTER_SLAVE -> {
                MasterSlaveServersConfig masterSlaveServersConfig = config.useMasterSlaveServers();
                if (StringUtils.hasText(properties.getMaster().getAddress())) {
                    masterSlaveServersConfig.setMasterAddress(properties.getMaster().getAddress());
                }
                if (StringUtils.hasText(properties.getSlave().getAddress())) {
                    masterSlaveServersConfig.setSlaveAddresses(Set.of(splitAddress(properties.getSlave().getAddress())));
                }
                masterSlaveServersConfig.setDatabase(database);
                baseConfig = masterSlaveServersConfig;
            }
            case SENTINEL -> {
                SentinelServersConfig sentinelServersConfig = config.useSentinelServers();
                sentinelServersConfig.setSentinelAddresses(List.of(splitAddress(serverAddress)));
                sentinelServersConfig.setMasterName(properties.getSentinel().getMasterName());
                sentinelServersConfig.setDatabase(database);
                baseConfig = sentinelServersConfig;
            }
            case CLUSTER -> {
                ClusterServersConfig clusterServersConfig = config.useClusterServers();
                clusterServersConfig.addNodeAddress(splitAddress(serverAddress));
                baseConfig = clusterServersConfig;
            }
            default -> throw new BeanCreationException("ServerType " + serverType + " not support");
        }
        if (StringUtils.hasText(username)) {
            baseConfig.setUsername(username);
        }
        if (StringUtils.hasText(password)) {
            baseConfig.setPassword(password);
        }
        baseConfig.setConnectTimeout(properties.getConnectTimeoutMillis());
        baseConfig.setTimeout(properties.getSocketTimeoutMillis());
        baseConfig.setKeepAlive(properties.isKeepAlive());
        baseConfig.setRetryAttempts(properties.getRetryAttempts());
        baseConfig.setRetryInterval(properties.getRetryIntervalMillis());
        baseConfig.setIdleConnectionTimeout(properties.getMaxIdleMillis());
        if (baseConfig instanceof BaseMasterSlaveServersConfig<?> baseMasterSlaveServersConfig) {
            final int masterMaxPoolSize = Objects.nonNull(properties.getMaster()) ? properties.getMaster().getMaxPoolSize() : properties.getMaxPoolSize();
            final int masterMinIdleSize = Objects.nonNull(properties.getMaster()) ? properties.getMaster().getMinIdleSize() : properties.getMinIdleSize();
            final int slaveMaxPoolSize = Objects.nonNull(properties.getSlave()) ? properties.getSlave().getMaxPoolSize() : properties.getMaxPoolSize();
            final int slaveMinIdleSize = Objects.nonNull(properties.getSlave()) ? properties.getSlave().getMinIdleSize() : properties.getMinIdleSize();
            baseMasterSlaveServersConfig.setMasterConnectionPoolSize(masterMaxPoolSize);
            baseMasterSlaveServersConfig.setMasterConnectionMinimumIdleSize(masterMinIdleSize);
            baseMasterSlaveServersConfig.setSlaveConnectionPoolSize(slaveMaxPoolSize);
            baseMasterSlaveServersConfig.setSlaveConnectionMinimumIdleSize(slaveMinIdleSize);
            baseMasterSlaveServersConfig.setIdleConnectionTimeout(properties.getMaxIdleMillis());
        }
        objectProvider.ifAvailable(customizers -> Arrays.stream(customizers).forEach(c -> c.customize(baseConfig)));
        return Redisson.create(config);
    }

    private String[] splitAddress(String address) {
        if (!StringUtils.hasText(address)) {
            return new String[]{};
        }
        return (String[]) Arrays.stream(address.split("[,;]")).map(addr -> checkAndFixAddress(address)).toArray();
    }

    private String checkAndFixAddress(String address) {
        final String protocol = "redis://";
        final String sslProtocol = "rediss://";
        if (address.startsWith(protocol) || address.startsWith(sslProtocol)) {
            return address;
        }
        return protocol + address;
    }

}
